#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include "thread.hpp"
#include "lockGuard.hpp"

// 本质上就是一个生产消费者模型
// 主要是生产接口和消费接口
const int g_thread_num = 3;

template <class T>
class ThreadPool
{
public:
  // 方便外部使用的接口，如在static函数里使用this，也算是在外部使用
  pthread_mutex_t *getMutex()
  {
    return &lock;
  }
  bool isEmpty()
  {
    return task_queue_.empty();
  }
  void waitCond()
  {
    pthread_cond_wait(&cond, &lock);
  }
  T getTask()
  {
    T t = task_queue_.front();
    task_queue_.pop();
    return t;
  }

private:                                                       // 设为私有，外部没办法构造
  ThreadPool(int thread_num = g_thread_num) : num_(thread_num) // 初始化
  {
    pthread_mutex_init(&lock, nullptr);
    pthread_cond_init(&cond, nullptr);
    // 这里初始化已经完成了，对象已经存在了

    // 赋值，对象已经存在了，可以用this了
    for (int i = 1; i <= num_; i++)
    {
      threads_.push_back(new Thread(i, routine, this /*因为static函数没有this，却想要访问类内成员，所以要传this*/));
    }
  }

  ThreadPool(const ThreadPool<T> &other) = delete; // 删掉了，不会默认生成拷贝构造函数
  const ThreadPool<T>& operator=(const ThreadPool<T> &other) = delete;

public:



  //懒汉模式单例线程
  // 考虑多线程使用单例的过程，所以也要加锁

  static ThreadPool<T> *getThreadPool(int num = g_thread_num) // 非静态函数不能访问静态成员变量，所以要设为static
  {
    if (nullptr == thread_ptr)//减少加锁和解锁的浪费,
    {
      // 可以有效减少未来必定要进行加锁检测的问题,
      //拦截大量的在已经创建好单例的时候，剩余线程请求单例的而直接访问锁的行为。

      lockGuard lockguard(&mutex);
      // pthread_mutex_lock(&mutex);
      //存在问题1.
      // 但是，未来任何一个线程想获取单例,都必须调用getThreadPool接口
      // 但是，一定会存在大量的申请和释放锁的行为，这个是无用且浪费的，所以要用双层if来减少浪费

      if (nullptr == thread_ptr) // 没必要while
      {
        thread_ptr = new ThreadPool<T>(num);
      }
      // pthread_mutex_unlock(&mutex);
    }
    return thread_ptr; // 永远都是同一个线程池对象
  }



  // 1.run
  void run()
  {
    for (auto &iter : threads_)
    {
      iter->start();
      // std::cout<<iter->name()<<"启动成功"<<std::endl;
      logMessage(NORMAL, "%s %s", iter->name().c_str(), "启动成功");
    }
  }




  // 2.生产过程-pushTask
  void pushTask(const T &task) // 由于STL容器和容器适配器都不是线程安全的，所以要加锁保护
  {
    lockGuard lockgurad(&lock);
    task_queue_.push(task);
    pthread_cond_signal(&cond); // 唤醒消费者

    sleep(1);
  }




  // 消费过程-routine
  // 线程的例程函数，会传给线程对象
  static void *routine(void *args)
  // 注：1.因为隐藏了个this指针，所以改函数类型实际上是void*(void* ,void* ),要加static去掉this指针，变成void*(void* )。才能传进线程对象Thread中构造出线程对象。
  // this指针可以显示调用，但是不用显示传，函数对象调用成员函数，或者成员变量时候其实已经隐藏传了本身的地址（本身的地址就是this)。
  //     2.改成static之后，static函数对象不能访问类中非静态成员变量，因为类中的非静态成员变量属于类，需要this指针才能访问到。
  //      解决方法：法1.把要访问的成员变量设置成static。法2.把该static函数设为该类的友元函数 法3.传this指针进来。
  {
    ThreadData *td = (ThreadData *)args;
    ThreadPool<T> *tp = (ThreadPool<T> *)td->args_; // 线程传过来的里面的参数-this指针
    T task;

    while (true)
    {
      // tp->show();
      // sleep(1);

      // std::cout<<"我是一个线程,我的名字是："<<td->name_<<std::endl;
      // sleep(1);

      // 正式：
      // lock
      // while(task_queue.empty())wait();
      // 获取任务
      // unlock

      // 处理任务

      { // 代码块，里面的是一个局部区域，里面的局部变量生命周期在这里面
        // 1.lock
        lockGuard lockgurad(tp->getMutex());
        // 2.条件判断
        while (tp->isEmpty())
          tp->waitCond(); // 条件满足，在这阻塞，并自动释放锁。条件不满足，被唤醒，重新得到锁。
        // 3.获取任务(共享空间，需要线程安全)
        task = tp->getTask(); // 任务队列是生产者和消费者共享的->将任务从共享，拿到自己的私有空间。
        // 4.unlock
      }
      // 5.处理任务(私有空间，已经线程安全，不需要加锁)
      task(td->name_);
    }
  }





  void joins() // test
  {
    for (auto &iter : threads_)
    {
      iter->join(); // 等待，让主线程回收线程的相关资源
    }
  }




  void show()
  {
    std::cout << "可以传入this，让静态方法的线程，访问到线程池里面的方法" << std::endl;
  }
  ~ThreadPool()
  {
    for (auto &iter : threads_)
    {
      iter->join(); // 等待，让主线程回收线程的相关资源
      delete iter;  // 释放在堆上申请的空间，去掉thread的析构函数
    }
    pthread_mutex_destroy(&lock);
    pthread_cond_destroy(&cond);
  }

private:
  // 方案1.
  std::vector<Thread *> threads_; // 线程队列
  int num_;                       // 线程个数
  std::queue<T> task_queue_;      // 任务队列-从线程池外面来

  static ThreadPool<T> *thread_ptr; // 属于所有类
  static pthread_mutex_t mutex;     // 静态或者全局的可以直接这样初始化，不需要init

  // 方案2.
  // queue1,queue2;
  // std::queue<T> *p_queue,*c_queue;
  // p_queue->queue1
  // c_queue->queue2
  // if(p_queue.emtpy()||c_queue.isfull())swap(p_queue,c_queue);
  // 或p_queue->生产一批任务之后，swap(p_queue,c_queue),唤醒所有线程/一个线程
  // 当消费者处理完毕的时候，也可以进行swap(p_queue,c_queue);
  // 因为我们生产和消费用的是不同的队列，未来对我们要进行资源的处理的时候，仅仅是指针。各自在各自的队列中生产，相互不干扰，需要加锁的地方仅仅在swap()处，也可以派一个线程来监管q_queue和c_queue;

  pthread_mutex_t lock; // 保证task_queue的线程安全
  pthread_cond_t cond;  // 用于消费者检测task队列中是否有任务，有就被唤醒，没有就阻塞
};

template <typename T>
ThreadPool<T> *ThreadPool<T>::thread_ptr = nullptr;
template <typename T>
pthread_mutex_t ThreadPool<T>::mutex =PTHREAD_MUTEX_INITIALIZER;